package org.ezaero.jmimic.impl.store.xstream;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import org.ezaero.jmimic.api.MimicConfig;
import org.ezaero.jmimic.impl.store.Invocation;
import org.ezaero.jmimic.impl.store.MimicStore;
import org.ezaero.jmimic.impl.store.MimicStoreException;

public class XStreamMimicStore implements MimicStore {

    public static final byte[] FILE_START_TOKEN = "MIMICXXX".getBytes();
    public static final byte[] FILE_SEPARATOR_TOKEN = "\nMMCSEP:".getBytes();
    public static final byte[] FILE_END_TOKEN = "\nXCIMIM\n".getBytes();

    private final MimicConfig config;
    private final File file;
    private final boolean isRecording;
    private final DataInputStream reader;
    private final DataOutputStream writer;

    public XStreamMimicStore(MimicConfig config, String name) {
        config.getOutputDir().mkdirs();

        this.config = config;
        this.file = new File(config.getOutputDir(), name + ".mimic." + config.getFormat().toString().toLowerCase() + (config.isCompressed() ? ".gz" : ""));
        this.isRecording = !this.file.exists() || config.isForceOverwrite();

        if (isRecording) {
            reader = null;
            try {
                final OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file));
                writer = new DataOutputStream(config.isCompressed() ? new GZIPOutputStream(outputStream) : outputStream);
                writer.write(FILE_START_TOKEN);
            } catch (IOException e) {
                throw new MimicStoreException("Failed to create writer for file:" + file, e);
            }
        } else {
            try {
                final InputStream inputStream = new BufferedInputStream(new FileInputStream(file));
                reader = new DataInputStream(config.isCompressed() ? new GZIPInputStream(inputStream) : inputStream);
                writer = null;

                final byte[] buf8 = new byte[8];
                reader.read(buf8);

                if (!Arrays.equals(FILE_START_TOKEN, buf8)) {
                    throw new MimicStoreException("Invalid mimic store file.");
                }

            } catch (IOException e) {
                throw new MimicStoreException("Failed to create reader for file:" + file, e);
            }
        }
    }

    public void close() {
        if (reader != null) {
            try {
                final byte[] buf8 = new byte[8];
                reader.read(buf8);
                
                if (!Arrays.equals(FILE_END_TOKEN, buf8)) {
                    throw new MimicStoreException("Mimic store closed prematurely.");
                }
                
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (writer != null) {
            try {
                writer.write(FILE_END_TOKEN);
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public boolean isRecording() {
        return isRecording;
    }

    @Override
    public Invocation read(String method, Object[] args) {
        if (reader == null) {
            throw new MimicStoreException("Store is currently only writable.");
        }

        Invocation invocation = null;
        try {
            final byte[] buf8 = new byte[8];

            reader.read(buf8);

            if (!Arrays.equals(FILE_SEPARATOR_TOKEN, buf8)) {
                throw new MimicStoreException("Mimic file entires are corrupt. Separator not in expected location.");
            }

            Arrays.fill(buf8, 0, 8, (byte) 0);

            reader.read(buf8);
            final int size = Integer.valueOf(new String(buf8), 16);

            final byte[] buf = new byte[size];
            reader.readFully(buf);

            final ByteArrayInputStream bais = new ByteArrayInputStream(buf);
            invocation = (Invocation) config.getXStream().fromXML(bais);
            bais.close();
        } catch (IOException e) {
            throw new MimicStoreException("Failed to read invocation from mimic file: " + file, e);
        }

        return invocation;
    }

    @Override
    public void write(String method, Object[] args, Object rval) {
        if (writer == null) {
            throw new IllegalStateException("Store is currently only readable.");
        }

        try {
            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
            config.getXStream().toXML(new Invocation(method, args, rval), baos);
            baos.close();

            writer.write(FILE_SEPARATOR_TOKEN);
            writer.writeBytes(String.format("%08X", baos.size()));
            baos.writeTo(writer);
        } catch (IOException e) {
            throw new MimicStoreException("Failed to write invocation to mimic file: " + file, e);
        }
    }
}
